/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System System Specific Module                         */
/*                                                                           */
/*    Copyright (C) 2010 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    netBufer - Network packet buffer implementation                        */
/*****************************************************************************/

//! \defgroup ethernet Ethernet packet buffer

///////////////////////////////////////////////////////////////////////////////
// Includes
#include <netPacketBuffer.h>
#include <netIP.h>
#include <netMAC.h>

///////////////////////////////////////////////////////////////////////////////
// Constants


///////////////////////////////////////////////////////////////////////////////
// Type definitions


/*****************************************************************************/
/* Local functions                                                           */
/*****************************************************************************/
static void netFindFreeBufferFromTheEnd( netChunkIndex* out_index, dosByte* out_free_chunks);
static netChunkIndex netFindFreeBuffer(void);

/*****************************************************************************/
/* Global variables                                                          */
/*****************************************************************************/

// Chunk buffer
netPacketEntry g_netBuffer[netCHUNK_COUNT];

/*****************************************************************************/
/* Module global variables                                                   */
/*****************************************************************************/

// RX variables
static volatile netChunkIndex l_rx_packet_start;

static netChunkIndex l_rx_store_first_chunk;
static netChunkIndex l_rx_store_chunk_index;

static netChunkIndex l_rx_retrieve_first_chunk;
static netChunkIndex l_rx_retrieve_chunk_index;
static dosByte l_rx_retrieve_chunk_pos;

// TX variables
static volatile netChunkIndex l_tx_retrieve_chunk_index;
static dosByte l_tx_retrieve_chunk_pos;

static dosBool l_tx_packet_start;

static netChunkIndex l_tx_store_first_chunk;
static netChunkIndex l_tx_store_chunk_index;
static dosByte l_tx_store_chunk_pos;

/*****************************************************************************/
/* Public functions                                                          */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Initializes network packet buffer. Must be called before any other buffer call.
void netBufferInitialize(void)
{
	netChunkIndex i;
	
	// set all chunks to free
	for( i = 0; i < netCHUNK_COUNT; i++ )
	{
    g_netBuffer[i].Status      = netCS_EMPTY;
		g_netBuffer[i].Type				= netPT_UNKNOWN;
		g_netBuffer[i].NextChunk   = netINVALID_CHUNK;
		g_netBuffer[i].UsedLength  = 0;
	}
		
	// initialize other variables
  l_rx_store_first_chunk         = netINVALID_CHUNK;
	l_rx_packet_start          = netINVALID_CHUNK;
  l_rx_retrieve_chunk_index = netINVALID_CHUNK;
	l_tx_retrieve_chunk_index = netINVALID_CHUNK;
	l_tx_retrieve_chunk_pos   = 0;
	l_tx_packet_start         = dosFalse;
	l_tx_store_chunk_index    = netINVALID_CHUNK;
	l_tx_store_first_chunk    = netINVALID_CHUNK;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns free buffer size on bytes.
//! \return Buffer size
dosWord netGetFreeBufferSize(void)
{
  netChunkIndex index;
  dosWord size;

  // summ up the size of free chunks
  size = 0;
  for( index = 0; index < netCHUNK_COUNT; index++ )
  {
    if( g_netBuffer[index].Status == netCS_EMPTY )
      size += netCHUNK_SIZE;
  }

  return size;
}

/*****************************************************************************/
/* Receiver functions                                                        */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Creates (begins) a received packet.
//! Usually this function is called from the receiver interrupt handler.
//! \param Type of the packet
//! \return True if packet created, false when no buffer available
dosBool netCreateRXPacket( dosByte in_type )
{
	netChunkIndex chunk_index;
	dosByte free_chunks;
	
	// refuse new packet until we finish the actual one
	if( l_rx_store_first_chunk != netINVALID_CHUNK )
		return dosFalse;

	// get first chunk index		
	netFindFreeBufferFromTheEnd( &chunk_index, &free_chunks );
	if( chunk_index == netINVALID_CHUNK )
		return dosFalse;
		
	// init chunk
  g_netBuffer[chunk_index].Status      = netCS_RESERVED;
  g_netBuffer[chunk_index].Type        = in_type;
	g_netBuffer[chunk_index].NextChunk   = netINVALID_CHUNK;
	g_netBuffer[chunk_index].id = 1;
	g_netBuffer[chunk_index].UsedLength  = 0;

	// store chunk index	
	l_rx_store_first_chunk = chunk_index;
	l_rx_store_chunk_index = chunk_index;
		
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Stores one received byte into the RX buffer
//! netCreateRXPacket must be called before calling this function.
//! Usually this function is called from the receiver interrupt handler.
//! \param Data to store
//! \return True if character stored successfully
dosBool netWriteByteRXPacket( dosByte in_data )
{
	netChunkIndex chunk_index;
	dosByte free_chunks;
	dosByte chunk_pos;
	
	// sanity check
	if( l_rx_store_chunk_index == netINVALID_CHUNK )
		return dosFalse;
		
	// check free space
	if( g_netBuffer[l_rx_store_chunk_index].UsedLength >= netCHUNK_SIZE)
	{
		// reserve another chunk
		netFindFreeBufferFromTheEnd( &chunk_index, &free_chunks );
		if( chunk_index == netINVALID_CHUNK )
			return dosFalse;
			
		// init chunk
    g_netBuffer[chunk_index].Status      = netCS_RESERVED;
		g_netBuffer[chunk_index].Type				= netPT_UNKNOWN;
	  g_netBuffer[chunk_index].NextChunk   = netINVALID_CHUNK;
		g_netBuffer[chunk_index].id = 2;
	  g_netBuffer[chunk_index].UsedLength  = 0;				
			
		// update chunk index
		g_netBuffer[l_rx_store_chunk_index].NextChunk = chunk_index;
		g_netBuffer[l_rx_store_chunk_index].id = 3;
		l_rx_store_chunk_index = chunk_index;
	}
	
	// store data
	chunk_pos = g_netBuffer[l_rx_store_chunk_index].UsedLength;
	g_netBuffer[l_rx_store_chunk_index].Buffer[chunk_pos++] = in_data;
	g_netBuffer[l_rx_store_chunk_index].UsedLength = chunk_pos;

	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Closes packet reception and stores current package
//! Usually this function is called from the receiver interrupt handler.
//! netCreateRXPacket must be called before calling this function.
//! \return True if packet closed successfully
dosByte netStoreRXPacket(void)
{
	netChunkIndex end_of_list;

  // change chunk flag to packet end
  g_netBuffer[l_rx_store_chunk_index].Status = netCS_PACKET_END;
	
	// find the end of the current RX packet list
	if( l_rx_packet_start == netINVALID_CHUNK )
	{
		// there are no RX packet on the list -> put current list at the beginning
		l_rx_packet_start = l_rx_store_first_chunk;
	} 
	else
	{
		// find the list of the already received packets
		end_of_list = l_rx_packet_start;
		
		while( g_netBuffer[end_of_list].NextChunk != netINVALID_CHUNK )
			end_of_list = g_netBuffer[end_of_list].NextChunk;
		
    // update last packet
		g_netBuffer[end_of_list].NextChunk = l_rx_store_first_chunk;
		g_netBuffer[end_of_list].id = 4;
	}

  // clear current packet list
	l_rx_store_first_chunk = netINVALID_CHUNK;
	
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Closes packet reception and stores current package with gives length
//! \param New length (must be smaller or same as the original length
//! \return True if packet closed successfully
dosByte netStoreAndSetLengthRXPacket(dosWord in_length)
{
	netChunkIndex chunk_index;
	netChunkIndex next_chunk;
	
	// sanity check
	if( l_rx_store_first_chunk == netINVALID_CHUNK )
		return dosFalse;
		
	if( in_length == 0 )
	{
		netCancelRXPacket();
		return dosFalse;
	}

	// skip full chunks
	chunk_index = l_rx_store_first_chunk;
	while( chunk_index != netINVALID_CHUNK && in_length >= netCHUNK_SIZE )
	{
		// check if this is the last chunk
		if( g_netBuffer[chunk_index].Status == netCS_PACKET_END )
			return dosFalse;
		
		chunk_index = g_netBuffer[chunk_index].NextChunk;
		in_length -= netCHUNK_SIZE;
	}

	// sanity check	
	if( chunk_index == netINVALID_CHUNK )
		return dosFalse;
	
	// adjust chunk used length
	l_rx_store_chunk_index = chunk_index;
	chunk_index = g_netBuffer[chunk_index].NextChunk;
	
	g_netBuffer[l_rx_store_chunk_index].UsedLength	= (dosByte)in_length;
	g_netBuffer[l_rx_store_chunk_index].Status			= netCS_PACKET_END;
	g_netBuffer[l_rx_store_chunk_index].NextChunk		= netINVALID_CHUNK;
		
	// free all next chunks
	while( chunk_index != netINVALID_CHUNK )
	{
		next_chunk = g_netBuffer[chunk_index].NextChunk;
    g_netBuffer[chunk_index].UsedLength		= 0;
    g_netBuffer[chunk_index].NextChunk		= netINVALID_CHUNK;
		g_netBuffer[chunk_index].Type					= netPT_UNKNOWN;
    g_netBuffer[chunk_index].Status				= netCS_EMPTY;
    
    chunk_index = next_chunk;
	}
	
	// store packet
	return netStoreRXPacket();
}

///////////////////////////////////////////////////////////////////////////////
//! Clears already received bytes
//! netCreateRXPacket must be called before calling this function. Usually this
//! functions is called from the receiver interrupt handler when the reception is
//! cancelled.
//! \return True if packet closed successfully
void netCancelRXPacket(void)
{
	netDeletePacket(l_rx_store_first_chunk);
  l_rx_store_chunk_index = l_rx_store_first_chunk = netINVALID_CHUNK;
}

///////////////////////////////////////////////////////////////////////////////
// Received package retrieval functions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
//! Open received package for retrieval. 
//! It always sets the retrieval pointers to the beginning of the packet and
//! and can be called again and again without calling netDeleteRXPacket function.
//! \return True when packet is successfully opened
dosBool netOpenRXPacket(void)
{
  // check if packet is available
  if( l_rx_packet_start == netINVALID_CHUNK )
    return dosFalse;

  // update chunk pointers
  l_rx_retrieve_first_chunk = l_rx_packet_start;
  l_rx_retrieve_chunk_index = l_rx_packet_start;
  l_rx_retrieve_chunk_pos = 0;

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Open first packet in the socket received packet queue
//! It always sets the retrieval pointers to the payload of the packet and
//! and returns packet information
//! \param Socket to read from
//! \param Packet information (filled out by this function)
//! \return True when packet is successfully opened
dosBool netOpenSocketRXPacket( netSOCKET in_socket, netPacketInfo* out_packet_info )
{
  // check if packet is available
  if( g_netSockets[in_socket].FirstPendingPacket == netINVALID_CHUNK )
    return dosFalse;

  // load packet info
  l_rx_retrieve_first_chunk = g_netSockets[in_socket].FirstPendingPacket;
  *out_packet_info = *((netPacketInfo*)g_netBuffer[l_rx_retrieve_first_chunk].Buffer);

  // update chunk pointers
  l_rx_retrieve_chunk_index = out_packet_info->PayloadChunkIndex;
  l_rx_retrieve_chunk_pos   = out_packet_info->PayloadChunkPos;

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Removes first available RX packet from the buffer
void netDeleteRXPacket(void)
{
  netChunkIndex index;
  netChunkIndex next_index;
  dosBool last_chunk;

	// go through chunks
  index = l_rx_packet_start;
  last_chunk = dosFalse;
  while( index != netINVALID_CHUNK && !last_chunk )
  {
    // check for last chunk of the packet
    if( g_netBuffer[index].Status == netCS_PACKET_END )
    {
      // update first packet index
      // but be sure the RX packet interrupt handler will not
      // change the NextChunk index
			do
			{
				l_rx_packet_start = *(volatile dosByte*)(&g_netBuffer[index].NextChunk);
			}	while( l_rx_packet_start == netINVALID_CHUNK && *(volatile dosByte*)(&g_netBuffer[index].NextChunk) != netINVALID_CHUNK );

      last_chunk = dosTrue;
    }

    // store next chunk index
    next_index = g_netBuffer[index].NextChunk;

    // invalidate chunk
    g_netBuffer[index].UsedLength	= 0;
    g_netBuffer[index].NextChunk		= netINVALID_CHUNK;
		g_netBuffer[index].id = 6;
		g_netBuffer[index].Type				= netPT_UNKNOWN;
    g_netBuffer[index].Status			= netCS_EMPTY;

    // next chunk
    index = next_index;
  }
}

///////////////////////////////////////////////////////////////////////////////
//! Deletes first packet in the socket received packet queue.
//! \param Socket to delete first waiting packet
void netDeleteSocketRXPacket(netSOCKET in_socket)
{
  // delete packet
  g_netSockets[in_socket].FirstPendingPacket = netDeletePacket( g_netSockets[in_socket].FirstPendingPacket );
}

///////////////////////////////////////////////////////////////////////////////
//! Retrieves one byte from the current RX packet
//! netOpenRXPacket must be called before calling this function
//! \param Buffer receiving the byte
//! \return True when byte is successfully returned (False at the end of the packet)
dosBool netReadByteRXPacket( dosByte* out_byte )
{
  // check chunk index
  if( l_rx_retrieve_chunk_index == netINVALID_CHUNK )
    return dosFalse;

  // check packet end
  if( g_netBuffer[l_rx_retrieve_chunk_index].Status == netCS_PACKET_END &&
      l_rx_retrieve_chunk_pos >= g_netBuffer[l_rx_retrieve_chunk_index].UsedLength )
    return dosFalse;

  // read byte
  *out_byte = g_netBuffer[l_rx_retrieve_chunk_index].Buffer[l_rx_retrieve_chunk_pos++];

  // move to the next chunk
  if( l_rx_retrieve_chunk_pos >= netCHUNK_SIZE )
  {
    l_rx_retrieve_chunk_index = g_netBuffer[l_rx_retrieve_chunk_index].NextChunk;
    l_rx_retrieve_chunk_pos = 0;
  }

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Retrieves one word from the current RX packet (word is returned in little endian order)
//! netOpenRXPacket must be called before calling this function
//! \param Buffer receiving the word
//! \return True when byte is successfully returned (False at the end of the packet)
dosBool netReadWordRXPacket( dosWord* out_word )
{
  dosByte buffer_high;
  dosByte buffer_low;

  if( !netReadByteRXPacket( &buffer_high ) )
    return dosFalse;

  if( !netReadByteRXPacket( &buffer_low ) )
    return dosFalse;

  *out_word = (buffer_high << 8) + buffer_low;

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Retrieves one double word from the current RX packet (double word is returned in little endian order)
//! netOpenRXPacket must be called before calling this function
//! \param Buffer receiving the word
//! \return True when byte is successfully returned (False at the end of the packet)
dosBool netReadDWordRXPacket( dosDWord* out_word )
{
  dosWord buffer_high;
  dosWord buffer_low;

  if( !netReadWordRXPacket( &buffer_high ) )
    return dosFalse;

  if( !netReadWordRXPacket( &buffer_low ) )
    return dosFalse;

  *out_word = (((dosDWord)buffer_high) << 16) + buffer_low;

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Retrieves block of bytes from the current RX packet
//! \param Buffer receiving data
//! \param Length of the buffer
//! \return True when block is successfully returned (False at the end of the packet)
dosBool netReadBlockRXPacket( dosByte* out_buffer, dosWord in_length )
{
  while( in_length > 0 )
  {
    // read byte, return if failed
    if( !netReadByteRXPacket( out_buffer++ ) )
      return dosFalse;

    in_length--;
  }

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Unlinks current (first) packet from the RX queue. 
//! The packet is not deleted from queue just unlinked so it must be deleted
//! separately.
//! \return First chunk id of the packet
netChunkIndex netUnlinkRXPacket(void)
{
  netChunkIndex first_chunk;
	netChunkIndex last_chunk;

  // check if packet available
  first_chunk = l_rx_packet_start;
  if( first_chunk == netINVALID_CHUNK )
    return netINVALID_CHUNK;

	// find the last chunk
	last_chunk = first_chunk;
	while( last_chunk != netINVALID_CHUNK && g_netBuffer[last_chunk].Status != netCS_PACKET_END )
		last_chunk = g_netBuffer[last_chunk].NextChunk;
		
	// sanity check
	if( last_chunk == netINVALID_CHUNK )
		return netINVALID_CHUNK;
		
  // update first packet index
  // but be sure the RX packet interrupt handler will not
  // change the NextChunk index
	do
	{
		l_rx_packet_start = *(volatile dosByte*)(&g_netBuffer[last_chunk].NextChunk);
	}	while( l_rx_packet_start == netINVALID_CHUNK && *(volatile dosByte*)(&g_netBuffer[last_chunk].NextChunk) != netINVALID_CHUNK );
	
	// close packet
	g_netBuffer[last_chunk].NextChunk = netINVALID_CHUNK;
	g_netBuffer[last_chunk].id = 8;
	g_netBuffer[last_chunk].Status = netCS_PACKET_END;	

  return first_chunk;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns the length of the first available RX packet
//! \return Length of the packet in bytes
dosWord netGetRXPacketLength(void)
{
  dosWord length;
  netChunkIndex index;

  // go through packet list
  index = l_rx_packet_start;
  length = 0;
  while( index != netINVALID_CHUNK )
  {
    // summ length
    length += g_netBuffer[index].UsedLength;

    // check for last chunk of the packet
    if( g_netBuffer[index].Status == netCS_PACKET_END )
      break;

    // next chunk
    index = g_netBuffer[index].NextChunk;
  }

  return length;
}

///////////////////////////////////////////////////////////////////////////////
//! Gets current RX packet retrieval position
//! \return Position in bytes
dosWord netGetRXPacketPos(void)
{
	dosWord pos;
	netChunkIndex chunk_index;
	
	// init
	pos = 0;
	
	// sanity check
	if( l_rx_retrieve_chunk_index != netINVALID_CHUNK )
	{	
		// add full chunks	
		chunk_index = l_rx_retrieve_first_chunk;
		while( chunk_index != netINVALID_CHUNK && chunk_index != l_rx_retrieve_chunk_index )
		{
			pos += netCHUNK_SIZE;
			chunk_index = g_netBuffer[chunk_index].NextChunk;
		}
		
		// add remaining bytes from chunk pos
		pos += l_rx_retrieve_chunk_pos;
	}
		
	return pos;
}	

///////////////////////////////////////////////////////////////////////////////
//! Sets RX packet retrieval position
//! \param New position in bytes
//! \return True if position was sussecfully updated
dosBool netSetRXPacketPos(dosWord in_pos)
{
	netChunkIndex chunk_index;
	
	// sanity check
	if( l_rx_retrieve_first_chunk == netINVALID_CHUNK )
		return dosFalse;
		
	// skip full chunks
	chunk_index = l_rx_retrieve_first_chunk;
	while( chunk_index != netINVALID_CHUNK && in_pos >= netCHUNK_SIZE )
	{
		// check if this is the last chunk
		if( g_netBuffer[chunk_index].Status == netCS_PACKET_END )
			return dosFalse;
			
		chunk_index = g_netBuffer[chunk_index].NextChunk;
		in_pos -= netCHUNK_SIZE;
	}
	
	// check length
	if( g_netBuffer[chunk_index].Status == netCS_PACKET_END && in_pos >= g_netBuffer[chunk_index].UsedLength )
		return dosFalse;

	// set retieval position		
	l_rx_retrieve_chunk_index = chunk_index;	
	l_rx_retrieve_chunk_pos	= (dosByte)in_pos;
		
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns current RX packet chunk pointer
//! \return Current chunk memory pointer
dosByte* netGetRXPacketChunkBuffer(void)
{
	return g_netBuffer[l_rx_retrieve_chunk_index].Buffer;
}

///////////////////////////////////////////////////////////////////////////////
//! Checks if received packets are available in the buffer
//! \return True when at least one packet is available
dosBool netIsRXPacketAvailable(void)
{
  return (l_rx_packet_start != netINVALID_CHUNK);
}

///////////////////////////////////////////////////////////////////////////////
//! Checks if received packets are available in the buffer
//! \return Type of the first available packet
netPacketType netGetAvailablePacketType(void)
{
  // check if packet is available
  if( l_rx_packet_start == netINVALID_CHUNK )
    return netPT_UNKNOWN;

  return g_netBuffer[l_rx_packet_start].Type;
}

/*****************************************************************************/
/* Transmitter functions                                                     */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Opens an existing packet as TX packet
//! \param First chunk index of the packet
//! \return True when successfully opened (false when a packet is already opened)
dosBool netOpenTXPacket( dosByte in_chunk_index )
{
	// check if other packet is opened
	if( l_tx_store_chunk_index != netINVALID_CHUNK )
		return dosFalse;
		
	// store packet information
	l_tx_store_first_chunk	= in_chunk_index;
	l_tx_store_chunk_index	= in_chunk_index;
	l_tx_store_chunk_pos		= 0;

	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Retrieves one byte from the current TX packet
//! netOpenRXPacket must be called before callin gthis function
//! \param Packet type
//! \return True when byte is successfully returned (False at the end of the packet)
dosBool netCreateTXPacket(netPacketType in_type)
{
  // sanity check
  if( l_tx_store_first_chunk != netINVALID_CHUNK )
     return dosFalse;

  // allocate buffer
  l_tx_store_first_chunk = netFindFreeBuffer();
  l_tx_store_chunk_index = l_tx_store_first_chunk;
  l_tx_store_chunk_pos = 0;
  if( l_tx_store_first_chunk == netINVALID_CHUNK )
    return dosFalse;

  // prepare chunk
  g_netBuffer[l_tx_store_chunk_index].Status      = netCS_PACKET_END;
  g_netBuffer[l_tx_store_chunk_index].NextChunk   = netINVALID_CHUNK;
  g_netBuffer[l_tx_store_chunk_index].UsedLength  = 0;
  g_netBuffer[l_tx_store_chunk_index].Type        = in_type;
  
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Clears already prepared TX packet
void netCancelTXPacket(void)
{
  netDeletePacket(l_tx_store_first_chunk);
	l_tx_store_first_chunk = l_tx_store_chunk_index	= netINVALID_CHUNK;
	l_tx_store_chunk_pos		= 0;
}

///////////////////////////////////////////////////////////////////////////////
//! Writes one byte to the current TX packet
//! netOpenTXPacket or netCreateTXPacket must be called before callin gthis function
//! \return True when byte is successfully written (False when no buffer available)
dosByte netWriteByteTXPacket( dosByte in_data )
{
  netChunkIndex new_chunk;

  // sanity check
  if( l_tx_store_chunk_index == netINVALID_CHUNK )
     return dosFalse;
 
  // change data byte
  g_netBuffer[l_tx_store_chunk_index].Buffer[l_tx_store_chunk_pos++] = in_data;

  // update used length
  if( g_netBuffer[l_tx_store_chunk_index].UsedLength < l_tx_store_chunk_pos )
    g_netBuffer[l_tx_store_chunk_index].UsedLength = l_tx_store_chunk_pos;

  // move to the next chunk
  if( l_tx_store_chunk_pos >= netCHUNK_SIZE )
  {
    // allocate new chunk if needed
    new_chunk = netINVALID_CHUNK;
    if( g_netBuffer[l_tx_store_chunk_index].NextChunk == netINVALID_CHUNK )
      new_chunk = netFindFreeBuffer();      

    if( new_chunk != netINVALID_CHUNK )
    {
      // prepare new chunk
      g_netBuffer[new_chunk].Status = netCS_PACKET_END;
      g_netBuffer[new_chunk].NextChunk = netINVALID_CHUNK;
      g_netBuffer[new_chunk].UsedLength = 0;

      // update old end chunk
      g_netBuffer[l_tx_store_chunk_index].NextChunk = new_chunk;
      g_netBuffer[l_tx_store_chunk_index].Status = netCS_RESERVED;
    }

    // update store chunk index
    l_tx_store_chunk_index = new_chunk;
    l_tx_store_chunk_pos = 0;
  }

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Writes one word to the current TX packet (word is written in big endian order)
//! netOpenTXPacket or netCreateTXPacket must be called before calling this function
//! \return True when data is successfully written (False when no buffer available)
dosBool netWriteWordTXPacket(dosWord in_data)
{
  if( netWriteByteTXPacket( HIGH(in_data) ) )
    return netWriteByteTXPacket( LOW(in_data) );
  else
    return dosFalse;
}

///////////////////////////////////////////////////////////////////////////////
//! Writes one double word to the current TX packet (double word is written in big endian order)
//! netOpenTXPacket or netCreateTXPacket must be called before calling this function
//! \return True when data is successfully written (False when no buffer available)
dosBool netWriteDWordTXPacket(dosDWord in_data)
{
  if( netWriteWordTXPacket( (dosWord)(in_data >> 16) ) )
    return netWriteWordTXPacket( (dosWord)(in_data & 0xffff) );
  else
    return dosFalse;
}

///////////////////////////////////////////////////////////////////////////////
//! Writes byte buffer to the current TX packet
//! netOpenTXPacket or netCreateTXPacket must be called before calling this function
//! \param Buffer of the bytes to write
//! \param Length of the buffer (in bytes)
//! \return True when data is successfully written (False when no buffer available)
dosBool netWriteBlockTXPacket( dosByte* in_buffer, dosWord in_length )
{
  dosBool success = dosTrue;

  while( in_length > 0 && success )
  {
    success = netWriteByteTXPacket( *in_buffer++ );
    in_length--;
  }

  return success;
}

///////////////////////////////////////////////////////////////////////////////
//! Writes const string to the current TX packet (terminating zero is not stored)
//! netOpenTXPacket or netCreateTXPacket must be called before calling this function
//! \param Buffer of the string to write
//! \param True when data is successfully written (False when no buffer available), if the value of this parameter is false the function will exit without doing anything
//!	\return Length of the added bytes
dosStringLength netWriteConstStringTXPacket( dosConstString in_buffer, dosBool* in_success )
{
	dosStringLength length = 0;

	// copy string
	while( *in_success && *in_buffer != '\0' )
	{ 
		*in_success = netWriteByteTXPacket( *in_buffer++ );
		length++;
	}

	return length;
}

///////////////////////////////////////////////////////////////////////////////
//! Writes string to the current TX packet (terminating zero is not stored)
//! netOpenTXPacket or netCreateTXPacket must be called before calling this function
//! \param Buffer of the string to write
//! \param True when data is successfully written (False when no buffer available), if the value of this parameter is false the function will exit without doing anything
//!	\return Length of the added bytes
dosStringLength netWriteStringTXPacket( dosString in_buffer, dosBool* in_success )
{
	dosStringLength length = 0;

	// copy string
	while( *in_success && *in_buffer != '\0' )
	{ 
		*in_success = netWriteByteTXPacket( *in_buffer++ );
		length++;
	}

	return length;
}

///////////////////////////////////////////////////////////////////////////////
//! Gets current TX packet store position
//! \return Position in bytes
dosWord netGetTXPacketPos(void)
{
	dosWord pos;
	netChunkIndex chunk_index;
	
	// init
	pos = 0;
	
	// sanity check
	if( l_tx_store_first_chunk != netINVALID_CHUNK )
	{	
		// add full chunks	
		chunk_index = l_tx_store_first_chunk;
		while( chunk_index != netINVALID_CHUNK && chunk_index != l_tx_store_chunk_index )
		{
			pos += netCHUNK_SIZE;
			chunk_index = g_netBuffer[chunk_index].NextChunk;
		}
		
		// add remaining bytes from chunk pos
		pos += l_tx_store_chunk_pos;
	}
		
	return pos;
}	

///////////////////////////////////////////////////////////////////////////////
//! Sets TX packet store position
//! \param New position in bytes
//! \return True if position was sussecfully updated
dosBool netSetTXPacketPos(dosWord in_pos)
{
	netChunkIndex chunk_index;
	
	// sanity check
	if( l_tx_store_first_chunk == netINVALID_CHUNK )
		return dosFalse;
		
	// skip full chunks
	chunk_index = l_tx_store_first_chunk;
	while( chunk_index != netINVALID_CHUNK && in_pos >= netCHUNK_SIZE )
	{
		// check if this is the last chunk
		if( g_netBuffer[chunk_index].Status == netCS_PACKET_END )
			return dosFalse;
			
		chunk_index = g_netBuffer[chunk_index].NextChunk;
		in_pos -= netCHUNK_SIZE;
	}
	
	// check length
	if( g_netBuffer[chunk_index].Status == netCS_PACKET_END && in_pos >= g_netBuffer[chunk_index].UsedLength )
		return dosFalse;

	// set retieval position		
	l_tx_store_chunk_index	= chunk_index;	
	l_tx_store_chunk_pos		= (dosByte)in_pos;
		
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Links packet into the list of  TX packets
//! \param Index of the first chunk of the packet to transmit
void netLinkTXPacket( netChunkIndex in_chunk_index )
{
	netChunkIndex index;
	netChunkIndex last_tx_packet;
	
	do
	{
		// cache first tx packet index
		last_tx_packet = l_tx_retrieve_chunk_index;
		
		// if there are no packets waiting link this packet to the beginning
		if( last_tx_packet == netINVALID_CHUNK )
		{
			l_tx_packet_start     = dosTrue;
      l_tx_retrieve_chunk_pos   = 0;
			l_tx_retrieve_chunk_index = in_chunk_index;

			break;
		}
				
		// find the end of the TX list
		index = last_tx_packet;
		while( g_netBuffer[index].NextChunk != netINVALID_CHUNK && l_tx_retrieve_chunk_index != netINVALID_CHUNK )
			index = g_netBuffer[index].NextChunk;
		
		// update the link of last chunk
		if( l_tx_retrieve_chunk_index != netINVALID_CHUNK )
			g_netBuffer[index].NextChunk = in_chunk_index;
		
		// loop if the interrupt routine has modified the retrieve chunk index
	}	while( l_tx_retrieve_chunk_index == netINVALID_CHUNK );

  // close TX packet storing
  l_tx_store_chunk_index = netINVALID_CHUNK;
}

///////////////////////////////////////////////////////////////////////////////
//! Transmits current TX packet
//! \return True if packet transmission started
dosBool netSendTXPacket(void)
{
	// sanity check
	if( l_tx_store_first_chunk == netINVALID_CHUNK )
		return dosFalse;

  // link TX packet into the transmission queue
  netLinkTXPacket( l_tx_store_first_chunk );

  // reset store index
  l_tx_store_first_chunk = l_tx_store_chunk_index = netINVALID_CHUNK;
  l_tx_store_chunk_pos = 0;

  // start transmission	
	netMACStartTransmission();

  return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns current TX packet storage chunk pointer
//! \return Current chunk memory pointer
dosByte* netGetTXPacketChunkBuffer(void)
{
	return g_netBuffer[l_tx_store_chunk_index].Buffer;
}

///////////////////////////////////////////////////////////////////////////////
//! Checks if packets to transmit waiting in the buffer
//! \return True when at least one packet is available
dosBool netIsTXPacketAvailable(void)
{
  return (l_tx_retrieve_chunk_index != netINVALID_CHUNK);
}

///////////////////////////////////////////////////////////////////////////////
//! Checks if the current TX position is at the beginning of a packet
//! \return True at the beginning of the packet
dosBool netIsTXPacketStart(void)
{
	return l_tx_packet_start;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns TX packet protocol (can be called only at packet start
//! \return True at the beginning of the packet
netPacketType netGetTXPacketType(void)
{
	if( l_tx_retrieve_chunk_index != netINVALID_CHUNK )
		return g_netBuffer[l_tx_retrieve_chunk_index].Type;
	else
		return netPT_UNKNOWN;
}

///////////////////////////////////////////////////////////////////////////////
//! Reads (and removes) one byte from the current TX packet
//! netLinkAndTransmitTXPacket must be called before calling this function
//! \return True when byte is successfully returned (False at the end of the packet)
dosBool netReadByteTXPacket( dosByte* out_byte )
{
  netChunkIndex index;
  dosBool retval = dosTrue;

  // check chunk index
  if( l_tx_retrieve_chunk_index == netINVALID_CHUNK )
    return dosFalse;

	// invalidate packet start flag    
	l_tx_packet_start = dosFalse;

  // check for chunk end
  if( (g_netBuffer[l_tx_retrieve_chunk_index].Status == netCS_PACKET_END &&
      l_tx_retrieve_chunk_pos >= g_netBuffer[l_tx_retrieve_chunk_index].UsedLength) ||
      l_tx_retrieve_chunk_pos >= netCHUNK_SIZE )
  {
    // store index
    index = l_tx_retrieve_chunk_index;

    // switch to the next chunk
		l_tx_retrieve_chunk_index = g_netBuffer[l_tx_retrieve_chunk_index].NextChunk;

    // return false only when packet is ended
		if(g_netBuffer[index].Status == netCS_PACKET_END )
		{
      // set packet start if there are more packets pending
      if( l_tx_retrieve_chunk_index != netINVALID_CHUNK )
			  l_tx_packet_start = dosTrue;

			retval = dosFalse;
		}

    // empty chunk
		g_netBuffer[index].UsedLength	= 0;
		g_netBuffer[index].NextChunk	= netINVALID_CHUNK;
		g_netBuffer[index].id = 7;
		g_netBuffer[index].Type				= netPT_UNKNOWN;
		g_netBuffer[index].Status			= netCS_EMPTY;
    
    // reset position
    l_tx_retrieve_chunk_pos = 0;
	}
	
  // read byte
  if( retval )
    *out_byte = g_netBuffer[l_tx_retrieve_chunk_index].Buffer[l_tx_retrieve_chunk_pos++];

  return retval;
}

///////////////////////////////////////////////////////////////////////////////
//! Retrieves one byte from TX packet buffer without removing it
//! \param Buffer to receive oen byte
//! \return True if byte successfully returned
dosBool netRetrieveByteTXPacket( dosByte* out_data )
{
  // sanity check
  if( l_tx_store_chunk_index == netINVALID_CHUNK )
     return dosFalse;
 
  // change data byte
  *out_data = g_netBuffer[l_tx_store_chunk_index].Buffer[l_tx_store_chunk_pos++];

  if( l_tx_store_chunk_pos >= netCHUNK_SIZE )
  {
    l_tx_store_chunk_index = g_netBuffer[l_tx_store_chunk_index].NextChunk;
    l_tx_store_chunk_pos = 0;
  }

  return dosTrue;
}

/*****************************************************************************/
/* CRC calculation functions                                                 */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Updates CRC value within TX packet buffer
//! \param Number of bytes used to update CRC
//! \param Pointer to CRC value
void netUpdateTXPacketChecksum( dosWord in_length, dosDWord* inout_checksum)
{
  dosDWord checksum = *inout_checksum;

  // sanity check
  if( l_tx_store_chunk_index == netINVALID_CHUNK )
     return;
 
  // if the length is odd than add first byte alone and add all consecutive bytes as word
  if( (in_length & 1) != 0 )
  {
    checksum += g_netBuffer[l_tx_store_chunk_index].Buffer[l_tx_store_chunk_pos++];
    in_length--;

    if( l_tx_store_chunk_pos >= netCHUNK_SIZE )
    {
      l_tx_store_chunk_index = g_netBuffer[l_tx_store_chunk_index].NextChunk;
      l_tx_store_chunk_pos = 0;
    }
  }
       
  if( l_tx_store_chunk_index == netINVALID_CHUNK )
     return;

  // build the sum of 16bit words
  while(in_length > 0)
  {
    checksum += (((dosWord)(g_netBuffer[l_tx_store_chunk_index].Buffer[l_tx_store_chunk_pos]))<<8) + g_netBuffer[l_tx_store_chunk_index].Buffer[l_tx_store_chunk_pos+1];
    l_tx_store_chunk_pos += 2;
    in_length -= 2;

    if( l_tx_store_chunk_pos >= netCHUNK_SIZE )
    {
      l_tx_store_chunk_index = g_netBuffer[l_tx_store_chunk_index].NextChunk;
      l_tx_store_chunk_pos = 0;
    }
  }
 
  // store checksum
  *inout_checksum = checksum;
}

///////////////////////////////////////////////////////////////////////////////
//! Updates CRC value within RX packet buffer
//! \param Number of bytes used to update CRC
//! \param Pointer to CRC value
void netUpdateRXPacketChecksum( dosWord in_length, dosDWord* inout_checksum)
{
  dosDWord checksum = *inout_checksum;

  // sanity check
  if( l_rx_retrieve_chunk_index == netINVALID_CHUNK )
     return;
 
  // build the sum of 16bit words
  while(in_length > 1)
  {
    checksum += (((dosWord)(g_netBuffer[l_rx_retrieve_chunk_index].Buffer[l_rx_retrieve_chunk_pos]))<<8) + g_netBuffer[l_rx_retrieve_chunk_index].Buffer[l_rx_retrieve_chunk_pos+1];
    l_rx_retrieve_chunk_pos += 2;
    in_length -= 2;

    if( l_rx_retrieve_chunk_pos >= netCHUNK_SIZE )
    {
      l_rx_retrieve_chunk_index = g_netBuffer[l_rx_retrieve_chunk_index].NextChunk;
      l_rx_retrieve_chunk_pos = 0;
    }
  }
 
  // if the length is odd than add first byte alone and add all consecutive bytes as word
  if( in_length > 0 )
  {
    if( l_rx_retrieve_chunk_index == netINVALID_CHUNK )
       return;

    checksum += g_netBuffer[l_rx_retrieve_chunk_index].Buffer[l_rx_retrieve_chunk_pos++] << 8;

    if( l_rx_retrieve_chunk_pos >= netCHUNK_SIZE )
    {
      l_rx_retrieve_chunk_index = g_netBuffer[l_rx_retrieve_chunk_index].NextChunk;
      l_rx_retrieve_chunk_pos = 0;
    }
  }
       
  // store checksum
  *inout_checksum = checksum;
}

/*****************************************************************************/
/* Other functions                                                           */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Moves current RX packet from RX queue into scoket RX queue
//! \param Socket to move the packet
//! \param Packet information structure
void netMoveRXPacketToSocketBuffer( netSOCKET in_socket, netPacketInfo* in_packet_info )
{
  netChunkIndex prev_index = netINVALID_CHUNK;
  netChunkIndex packet_chunk_index;
  netChunkIndex index;
  
  // update payload position
  in_packet_info->PayloadChunkIndex = l_rx_retrieve_chunk_index;
  in_packet_info->PayloadChunkPos   = l_rx_retrieve_chunk_pos;

  // unlink from the RX queue
  packet_chunk_index = netUnlinkRXPacket();

  // store packet info
 *((netPacketInfo*)g_netBuffer[packet_chunk_index].Buffer) = *in_packet_info;

  // link to the socket buffer
  index = g_netSockets[in_socket].FirstPendingPacket;
  if( index == netINVALID_CHUNK )
  {
    g_netSockets[in_socket].FirstPendingPacket = packet_chunk_index;
  }
  else
  {
    // find last chunk
    while( index != netINVALID_CHUNK )
    {
      prev_index = index ;
      index  = g_netBuffer[index].NextChunk;
    }

    g_netBuffer[prev_index].NextChunk = packet_chunk_index;
  }
}

///////////////////////////////////////////////////////////////////////////////
//! Deletes the given packet
//! \param First chunk index
//! \return Next chunk index
netChunkIndex netDeletePacket(netChunkIndex in_chunk)
{
  netChunkIndex next_index = netINVALID_CHUNK;

  while( in_chunk != netINVALID_CHUNK )
  {
    // clear chunks
    next_index = g_netBuffer[in_chunk].NextChunk;
		g_netBuffer[in_chunk].UsedLength	= 0;
    g_netBuffer[in_chunk].NextChunk  = netINVALID_CHUNK;
		g_netBuffer[in_chunk].Type				= netPT_UNKNOWN;
    g_netBuffer[in_chunk].Status			= netCS_EMPTY;
    in_chunk = next_index;
  }

  return next_index;
}

/*****************************************************************************/
/* Local functions                                                           */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Finds free chunk starting from the beginning of the chunk list
//! \return Free chunk index
static netChunkIndex netFindFreeBuffer(void)
{
	netChunkIndex index;
	netChunkIndex new_chunk;
  dosByte free_chunks;

	// search for free chunks
	index = 0;
	free_chunks = 0;
  new_chunk = netINVALID_CHUNK;
	while( index < netCHUNK_COUNT )
	{
		// check if chunk is free	
		if( g_netBuffer[index].Status == netCS_EMPTY )
		{
			// increment free counter
			free_chunks++;

      // store first free chunk index
			if( new_chunk == netINVALID_CHUNK )
				new_chunk = index;
		}

    index++;
	}

  // at least two free chunks are needed (to be sure the receiver allocation doesn't overlap with this allocation)
  if( free_chunks < 2 )
    return netINVALID_CHUNK;
  else
    return new_chunk;
}

///////////////////////////////////////////////////////////////////////////////
//! Finds free chunk starting from the end of the chunk list
//! \param Pointer to the variable receives free chunk index
//! \param Pointer to the variable receives free chunk count
static void netFindFreeBufferFromTheEnd( netChunkIndex* out_index, dosByte* out_free_chunks)
{
	netChunkIndex i;

	// search for free chunks
	i = netCHUNK_COUNT;
	*out_index = netINVALID_CHUNK;
	*out_free_chunks = 0;
	while( i > 0 )
	{
		i--;

		// check if chunk is free	
		if( g_netBuffer[i].Status == netCS_EMPTY )
		{
			// increment free counter
			(*out_free_chunks)++;
		
			// store first free chunk index
			if( *out_index == netINVALID_CHUNK )
				*out_index = i;
		}
	}
}
